package nancal.mp.service

import io.kubernetes.client.custom.V1Patch
import io.kubernetes.client.openapi.ApiClient
import io.kubernetes.client.openapi.ApiException
import io.kubernetes.client.openapi.apis.AppsV1Api
import io.kubernetes.client.openapi.apis.CoreV1Api
import io.kubernetes.client.openapi.models.V1Deployment
import nancal.mp.db.mongo.JenkinsLanguageEnum
import nancal.mp.db.mongo.entity.dev.HostIP
import nancal.mp.db.mongo.extend.getAppDeploySetting
import nancal.mp.db.mongo.mor
import nancal.mp.imageNameVersion
import nancal.mp.model.K8sAppServiceInfo
import nancal.mp.model.K8sAppServiceStatusInfo
import nancal.mp.model.K8sEventData
import nancal.mp.util.setEnvProfile
import nancal.mp.util.setNamespace
import nancal.mp.util.setPrivileged
import nancal.mp.util.tidyK8sYaml
import nbcp.base.comm.*
import nbcp.base.extend.*
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

@Service
class K8sDeploymentService : K8sBaseService() {
    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
    }

    fun list(
        targetEnv: String,
        namespace: String,
        name: String = "",
        productLineCode: String = "",
        isReady: Boolean? = null,
        language: JenkinsLanguageEnum? = null,
        versionMin: String = "",
        versionMax: String = "",
        skip: Int = 0,
        take: Int = 999
    ): ListResult<K8sAppServiceStatusInfo> {
        return list(
            getK8sClient(targetEnv),
            namespace,
            name,
            productLineCode,
            isReady,
            language,
            versionMin,
            versionMax,
            skip,
            take
        )
    }

    fun list(
        k8sConfig: ApiClient,
        namespace: String,
        name: String = "",
        productLineCode: String = "",
        isReady: Boolean? = null,
        language: JenkinsLanguageEnum? = null,
        versionMin: String = "",
        versionMax: String = "",
        skip: Int = 0,
        take: Int = 999
    ): ListResult<K8sAppServiceStatusInfo> {
        var api = AppsV1Api(k8sConfig)

        var labels = StringMap();


        if (language != null) {
            labels.put("language", language.toString())
        }

        var nameField = ""
        if (name.isNotEmpty()) {
            nameField = "metadata.name=${name}"
        }

        var list = try {
            api.listNamespacedDeployment(
                namespace.AsString("default")
            )
                .fieldSelector(nameField)
                .labelSelector(labels.map { it.key + "=" + it.value }.joinToString(","))
                .limit(999)
                .execute()
                .items

        } catch (ex: ApiException) {
            logger.error(ex.responseBody)
            throw ex;
        }

        var total = 0L;


        //当前的版本信息是最新的, 不是正在运行的.
        list
            .map { ret ->
                var image = ret?.spec?.template?.spec?.containers?.lastOrNull()?.image.AsString();

                var deployName = ret.metadata?.name.AsString()
                var item = getCurrentReplicaSetStatus(
                    k8sConfig,
                    ret.metadata?.namespace.AsString(), deployName,
                )
                item.name = deployName
                if (item.imageNameVersion == image.imageNameVersion) {
                    item.isLatestRsVersion = true;
                }
                return@map item
            }
            .filter {
                var deployName = it.name;
                var version = it.imageNameVersion
                if (name.HasValue && !deployName.contains(name)) {
                    return@filter false
                }

                if (versionMin.HasValue && (version < versionMin)) {
                    return@filter false;
                }

                if (versionMax.HasValue && (version > versionMax)) {
                    return@filter false;
                }

                return@filter true;
            }
            .run {
                if (isReady != null) {
                    if (isReady) {
                        return@run this.filter { it.imageNameVersion.HasValue && it.readyReplicas > 0 }
                    } else {
                        return@run this.filter { it.readyReplicas == 0 }
                    }
                }

                return@run this;
            }.run {
                total = this.size.toLong();
                if (take >= 0) {
                    return@run this.Slice(skip, skip + take)
                }
                return@run this.Slice(skip);
            }.apply {
                return ListResult.of(this, total)
            }
    }


    fun getCurrentReplicaSetStatus(
        targetEnv: String,
        namespace: String,
        deploymentName: String
    ): K8sAppServiceStatusInfo {
        return getCurrentReplicaSetStatus(getK8sClient(targetEnv), namespace, deploymentName)
    }

    fun getCurrentReplicaSetStatus(
        k8sConfig: ApiClient,
        namespace: String,
        deploymentName: String
    ): K8sAppServiceStatusInfo {
        var appApi = AppsV1Api(k8sConfig);

        var list = appApi.listNamespacedReplicaSet(
            namespace.AsString("default")
        )
            .labelSelector("app=${deploymentName}")
            .limit(999)
            .execute()
            .items;



        return list
            .filter { it.status?.readyReplicas.AsInt() > 0 }
            .map {
                var image = it.spec?.template?.spec?.containers?.lastOrNull()?.image.AsString();

                var imageHost = image.split("/").first();
                var imageImageVersion = image.Slice(imageHost.length + 1);

                var status = K8sAppServiceStatusInfo(
                    it.metadata?.name.AsString(),
                    imageHost,
                    imageImageVersion,
                    it.status?.replicas.AsInt(),
                    it.status?.readyReplicas.AsInt()
                )

                //判断是否是最新版本。
                var deploymentStatus =
                    appApi.readNamespacedDeployment(
                        deploymentName,
                        namespace.AsString("default")
                    )
                        .execute();

                var image1 =
                    deploymentStatus?.spec?.template?.spec?.containers?.lastOrNull()?.image.AsString();


                status.isLatestRsVersion = status.imageNameVersion == image1.imageNameVersion


                if (status.readyReplicas <= 0) {
                    status.lastEvent =
                        getEventReason(
                            k8sConfig,
                            namespace,
                            deploymentName,
                            it.metadata?.name.AsString()
                        )
                }

                return@map status;
            }
            .firstOrNull() ?: K8sAppServiceStatusInfo();

    }

    fun getEventReason(
        targetEnv: String,
        namespace: String,
        deploymentName: String,
        rsName: String
    ): K8sEventData {
        var api = getK8sClient(targetEnv)
        return getEventReason(api, namespace, deploymentName, rsName)
    }

    /**
     * 获取事件
     */
    fun getEventReason(
        k8sConfig: ApiClient,
        namespace: String,
        deploymentName: String,
        rsName: String
    ): K8sEventData {
        var coreApi = CoreV1Api(k8sConfig);
        var list = coreApi.listNamespacedEvent(
            namespace.AsString("default")
        )
            .limit(999)
            .execute()
            .items
        ;

        list
            .filter { it.involvedObject.fieldPath == "spec.contains.${deploymentName}" }
            .filter { it.involvedObject.name?.startsWith(rsName + "-") ?: false }
            .firstOrNull()
            .apply {
                if (this == null) return K8sEventData();
                return K8sEventData(
                    this.reason ?: "",
                    this.lastTimestamp.AsString().AsLocalDateTime()!!
                )
            }
    }

    fun applyDeploymentYaml(
        targetEnv: String,
        namespace: String,
        dockerName: String,
        deploymentYaml: String,
        envProfile: String = ""
    ): JsonResult {
        var appDeploySetting = mor.dev.appDeploySetting.getAppDeploySetting(targetEnv);

        return applyDeploymentYaml(
            getK8sClient(targetEnv),
            namespace,
            dockerName,
            deploymentYaml,
            envProfile,
            appDeploySetting.dockerBuildHosts
        )
    }

    fun applyDeploymentYaml(
        client: ApiClient,
        namespace: String,
        dockerName: String,
        deploymentYaml: String,
        envProfile: String = "",
        dockerHosts: List<HostIP> = listOf()
    ): JsonResult {
        var appApi = AppsV1Api(client);

        var map = deploymentYaml.FromYamlText<JsonMap>()

        if (namespace.HasValue) {
            map.setNamespace(namespace)
        }

        if (envProfile.HasValue) {
            map.setEnvProfile(envProfile)
        }


        if (dockerName.endsWith("-admin-api")) {
            map.setPrivileged()
        }

        //加载 host定义
        if (dockerHosts.filter { it.host.HasValue && it.ip.HasValue }.any()) {
            if (map.getValueByWbsPath("spec.template.spec.hostAliases") == null) {
                map.setValueByWbsPath("spec.template.spec.hostAliases", mutableListOf<JsonMap>())
            }

            var hosts = map.getValueByWbsPath("spec.template.spec.hostAliases") as MutableList<JsonMap>

            /*
        - hostnames:
            - amy
          ip: 1.1.1.1
             */

            hosts.addAll(
                dockerHosts
                    .filter { it.host.HasValue && it.ip.HasValue }
                    .map {
                        var item = JsonMap();
                        item.put("ip", it.ip)
                        item.put("hostnames", listOf(it.host))
                        return@map item;
                    }
            )
        }

//        val enableIstio = mor.dev.appDeploySetting.getAppDeploySetting(targetEnv).k8sConfig.enableIstio
//        if (!enableIstio) {
//            map.disableIstio()
//        }
        var content = map.ToYaml()

        var v1Deployment = try {
            V1Deployment.fromJson(content.FromYamlText(JsonMap::class.java).ToJson())
        } catch (e: Exception) {
            logger.error(content);
            throw e;
        }


        if (existsDeployment(client, namespace.AsString("default"), dockerName)) {
            try {
                var ns = appApi.replaceNamespacedDeployment(
                    dockerName,
                    namespace.AsString("default"),
                    v1Deployment
                )
                    .execute()

                if (ns == null) {
                    return JsonResult.error("创建失败")
                }
                return JsonResult()
            } catch (ex: ApiException) {


                /**
                 * 由于 K8s label 不同，导致无法部署，先删之。
                 */
                if (ex.code == 422 && ex.message!!.contains("field is immutable")) {
                    logger.warn("删除与之前冲突的 " + dockerName + " 的 Deployment Yaml 并重新部署!")


                    logger.warn(
                        """新的DeployYaml:
--------------------------------------
${content}
--------------------------------------
"""
                    )

                    appApi.deleteNamespacedDeployment(
                        dockerName, namespace.AsString("default")
                    ).execute()

                    Thread.sleep(500);
                } else {
                    logger.error(content)
                    logger.error(ex.responseBody)
                    throw ex;
                }
            }
        }

        try {
            var d = appApi.createNamespacedDeployment(
                namespace.AsString("default"), v1Deployment
            ).execute()

            if (d == null) {
                return JsonResult.error("创建失败")
            }
            return JsonResult()
        } catch (ex: ApiException) {

            logger.error(content)
            logger.error(ex.responseBody)
            throw ex;
        }
    }

    fun existsDeployment(targetEnv: String, namespace: String, name: String): Boolean {
        return existsDeployment(getK8sClient(targetEnv), namespace, name)
    }


    fun existsDeployment(client: ApiClient, namespace: String, name: String): Boolean {
        var appApi = AppsV1Api(client);


        var status = try {
            appApi.readNamespacedDeploymentStatus(
                name, namespace.AsString("default")
            ).execute()

        } catch (ex: ApiException) {
            return false;
        }

        if (status.status == null) {
            return false
        }

        return true;
    }

    fun delete(targetEnv: String, namespace: String, name: String): JsonResult {
        var k8sClient = getK8sClient(targetEnv)
        return delete(k8sClient, namespace, name)
    }

    fun delete(k8sClient: ApiClient, namespace: String, name: String): JsonResult {
        var appApi = AppsV1Api(k8sClient);

        var ret = appApi.deleteNamespacedDeployment(
            name, namespace.AsString("default")
        ).execute()


        logger.Important(ret.ToJson())

        return JsonResult()
    }

    fun getDeploymentYaml(targetEnv: String, namespace: String, name: String): JsonMap {
        return getDeploymentYaml(getK8sClient(targetEnv), namespace, name)
    }

    /**
     *
     */
    fun getDeploymentYaml(client: ApiClient, namespace: String, name: String): JsonMap {
        var appApi = AppsV1Api(client);
        var deploymentMap = try {
            appApi.readNamespacedDeployment(name, namespace.AsString("default"))
                .execute()

        } catch (ex: ApiException) {
            if (ex.code == 404) {
                return JsonMap()
            }

            throw ex;
        }.apply {
            if (this.spec == null) {
                return JsonMap()
            }
        }.ConvertType(JsonMap::class.java) as JsonMap?

        if (deploymentMap == null) return JsonMap()

        deploymentMap.tidyK8sYaml()

        return deploymentMap
    }

    fun getDeploymentInfo(targetEnv: String, namespace: String, name: String): ApiResult<K8sAppServiceInfo> {
        return getDeploymentInfo(getK8sClient(targetEnv), namespace, name)
    }

    fun getDeploymentInfo(client: ApiClient, namespace: String, name: String): ApiResult<K8sAppServiceInfo> {
        var appApi = AppsV1Api(client);


        try {
            var dep = appApi.readNamespacedDeployment(name, namespace.AsString("default"))
                .execute()


            var container = dep.spec.template.spec.containers.last()
            var fullImageVersion = container.image.AsString();

            var deployName = dep.metadata?.name.AsString()


            var ret = K8sAppServiceInfo();
            ret.name = deployName;
            ret.language = dep.metadata?.labels?.get("language").AsString().ToEnum<JenkinsLanguageEnum>()
                ?: JenkinsLanguageEnum.Normal
            ret.languageVersion = dep.metadata?.labels?.get("languageVersion").AsString()
            ret.fullImageVersion = fullImageVersion
            ret.replicas = dep?.status?.replicas ?: 0;
            ret.readyReplicas = dep?.status?.readyReplicas ?: 0;

            ret.appName = dep.metadata?.labels?.get("appName").AsString()
            if (ret.appName.isNullOrEmpty()) {
                ret.appName = container.name;
            }


            container.env?.forEach {
                ret.envs.put(it.name, it.value)
            }

            return ApiResult.of(ret);

        } catch (ex: ApiException) {
            if (ex.code == 404) {
                return ApiResult.error("找不到Deployment: ${name}")
            }

            throw ex;
        }
    }

    fun getPodList(
        targetEnv: String,
        namespace: String,
        serviceName: String,
        rsHash: String = ""
    ): List<String> {
        var k8sClient = getK8sClient(targetEnv)
        return getPodList(k8sClient, namespace, serviceName, rsHash)
    }

    /**
     * @param serviceName Service服务名称
     */
    fun getPodList(
        k8sClient: ApiClient,
        namespace: String,
        serviceName: String,
        rsHash: String = ""
    ): List<String> {
        var coreApi = CoreV1Api(k8sClient)

        try {
            coreApi.listNamespacedPod(
                namespace.AsString("default")
            )
                .limit(999)
                //不能看 deplyment yaml ， 要看 副本集 的Yaml！
                .labelSelector("name=${serviceName}")
                .execute()
                .items
                .map {
                    it?.metadata?.name.AsString()
                }
                .filter { it.HasValue }
                .apply {
                    return this;
                }

        } catch (ex: ApiException) {
            if (ex.code == 404) {
                return listOf();
            }

            throw ex;
        }
    }

    fun restart(targetEnv: String, namespace: String, serviceName: String): JsonResult {
        var k8sClient = getK8sClient(targetEnv)
        return restart(k8sClient, namespace, serviceName)
    }

    /**
     * 采用 Kuboard 重启策略。
     * 1. 记录当前副本。
     * 2. 扩容 +1
     * 3. 扩容成功后， 循环删除副本
     * @param serviceName: deploy-service
     */
    fun restart(k8sClient: ApiClient, namespace: String, serviceName: String): JsonResult {
        var coreApi = CoreV1Api(k8sClient)
        var oriPods = getPodList(k8sClient, namespace, serviceName)

        if (oriPods.any() == false) {
            return ApiResult.of(true)
        }


        coreApi.deleteCollectionNamespacedPod(
            namespace.AsString("default")
        )
            .labelSelector("name=${serviceName}")
            .execute()

        var runningPods = listOf<String>()
        /**
         * 循环30秒
         */
        for (i in 0..30) {
            Thread.sleep(1000);
            runningPods = getPodList(k8sClient, namespace, serviceName);
            if (oriPods.intersect(runningPods).any() == false) {
                return JsonResult();
            }
        }

        if (runningPods.any() == false) {
            return JsonResult.error("找不到可用的Pod")
        }


        if (oriPods.size != runningPods.size) {
            return JsonResult.error("${serviceName}未启动完成")
        }

        return JsonResult.error("重启${serviceName}失败")
    }

    fun setMemory(
        targetEnv: String,
        namespace: String,
        name: String,
        maxMi: Int
    ): JsonResult {
        var k8sClient = getK8sClient(targetEnv);
        return setMemory(k8sClient, namespace, name, maxMi)
    }

    /**
     * 设置内存
     */
    fun setMemory(
        k8sClient: ApiClient,
        namespace: String,
        name: String,
        maxMi: Int
    ): JsonResult {
        k8sClient.httpClient = k8sClient.httpClient.newBuilder()
            .addInterceptor(K8sPatchTypeInterceptor())
            .build();

        var coreApi = AppsV1Api(k8sClient)

        //[{"op":"replace","path":"/spec/replicas", "value": 3 }]

        var patchMax = JsonMap();
        patchMax.put("op", "replace")
        patchMax.put("path", "/spec/template/spec/containers/0/resources/limits/memory")
        patchMax.put("value", maxMi.toString() + "Mi")


        var body = V1Patch(listOf(patchMax).ToJson());

        var pod =
            coreApi.patchNamespacedDeployment(name, namespace, body).execute();

        if (pod == null) {
            return JsonResult.error("设置内存失败!");
        }

        logger.warn(pod.ToJson())
        return JsonResult();
    }

    fun setImageName(
        targetEnv: String,
        namespace: String,
        name: String,
        imageNameVersion: String
    ): JsonResult {
        var k8sClient = getK8sClient(targetEnv);
        return setImageName(k8sClient, namespace, name, imageNameVersion)
    }


    /**
     * 设置镜像名
     * 命令行是：
     * kubectl set image deployment/$deployment名称$  $容器名称$=$镜像名称$
     * kubectl set image deployment/risk-api  risk-api=reg2.huatuocloud.com/java/risk-api:20250905.145157
     * 第2个 risk-api 是容器名
     */
    fun setImageName(
        k8sClient: ApiClient,
        namespace: String,
        name: String,
        imageNameVersion: String
    ): JsonResult {
        k8sClient.httpClient = k8sClient.httpClient.newBuilder()
            .addInterceptor(K8sPatchTypeInterceptor())
            .build();

        var coreApi = AppsV1Api(k8sClient)

        //[{"op":"replace","path":"/spec/replicas", "value": 3 }]
        var patchRequst = JsonMap();

        //op 可以是  replace , add  , remove
        patchRequst.put("op", "replace")
        patchRequst.put("path", "/spec/template/spec/containers/0/image")
        patchRequst.put("value", imageNameVersion)


        var body = V1Patch(listOf(patchRequst).ToJson());

        var pod =
            coreApi.patchNamespacedDeployment(name, namespace.AsString("default"), body).execute();

        if (pod == null) {
            return JsonResult.error("设置内存失败!");
        }

        logger.warn(pod.ToJson())
        return JsonResult();
    }


    /**
     * 通过设置env变量，重启 Nginx
     */
    fun setEnv(
        targetEnv: String,
        namespace: String,
        deployment: String,
        index: Int,
        value: String
    ): JsonResult {

        //https://blog.csdn.net/tankpanv/article/details/103471945
        var k8sClient = getK8sClient(targetEnv);
        k8sClient.httpClient = k8sClient.httpClient.newBuilder()
            .addInterceptor(K8sPatchTypeInterceptor())
            .build();

        var coreApi = AppsV1Api(k8sClient)


//        var patchRequest = """
//{
//  "spec": {
//    "template": {
//      "spec": {
//        "containers": [
//          {
//            "env": [
//              {
//                "name": "updateAt",
//                "value": "${value}"
//              }
//            ]
//          }
//        ]
//      }
//    }
//  }
//}
//"""

        //https://datatracker.ietf.org/doc/html/rfc6902

        var patchRequst = JsonMap();
        patchRequst.put("op", "replace")
        patchRequst.put("path", "/spec/template/spec/containers/0/env/${index}/value")
        patchRequst.put("value", value)


        var body = V1Patch(listOf(patchRequst).ToJson());

        var pod =
            coreApi.patchNamespacedDeployment(
                deployment,
                namespace,
                body
            ).execute()

        if (pod == null) {
            return JsonResult.error("重启失败!");
        }

        logger.warn(pod.ToJson())

        return JsonResult();
    }
}










